package net.logax.games.chase.map;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;

import javax.crypto.spec.PSource;

import net.logax.games.chase.map.Map.MapFragment;

/**
 * Spieler startet vom Start von X,Y,Z aus in Richtung Y
 * 
 * TODO java.util.ConcurrentModificationException: Die EndPoint Collection darf
 * nicht bearbeitet werden!
 * 
 * @author mrduke
 * 
 */
public class MapBuilder {

	private static final int MAX_CORRIDOR_LENGTH = 8;

	private static final int MIN_CORRIDOR_LENGTH = 4;

	private Map.MapFragment[][][] mapArray;
	private int creationIndexX;
	private int creationIndexY;
	private int creationIndexZ;
	private Random rnd;
	public static final boolean DEBUG = true;

	public Map createMap() throws Exception {

		final Map map = new Map();
		mapArray = new Map.MapFragment[map.MAP_SIZE_X][map.MAP_SIZE_Y][map.MAP_SIZE_Z];
		map.setMapArray(mapArray);
		initEmpty(mapArray, map);

		int[] startPoint = markStartFields(map);
		int ITERATIONS = 5;

		Collection<int[]> initEndPoints = new ArrayList<int[]>();
		Collection<int[]> endPoints;
		{
			ArrayList<Map.MapFragment> firstCrossingPossibilities = new ArrayList<Map.MapFragment>();
			firstCrossingPossibilities.add(MapFragment.T_CROSSING_NX);
			firstCrossingPossibilities.add(MapFragment.T_CROSSING_NY);
			firstCrossingPossibilities.add(MapFragment.T_CROSSING_X);
			int countPossibilities = firstCrossingPossibilities.size();
			rnd = new Random();
			int usePossibilityIndex = rnd.nextInt(countPossibilities);
			MapFragment fragmentToAdd = firstCrossingPossibilities.get(usePossibilityIndex);
			{ // addEndPointsByFragment(previousCoordinates, currentCoordinates,
				// endPoints)

				int[] previousCoordinates = { startPoint[0], startPoint[1] - 1, startPoint[2] };
				int[] currentCoordinate = { startPoint[0], startPoint[1], startPoint[2] };
				setMapField(currentCoordinate[0], currentCoordinate[1], currentCoordinate[2], fragmentToAdd);
				endPoints = determineEndPoints(fragmentToAdd, previousCoordinates, currentCoordinate);
				initEndPoints.addAll(endPoints);
			}
		}
		boolean wasMapCreationSuccessful = false;
		rnd = new Random();
		Collection<int[]> nextEndPoints = new ArrayList<int[]>();
		for (int iteration = 1; iteration <= ITERATIONS /*
														 * &&
														 * !wasMapCreationSuccessful
														 */; iteration++) {
			if (iteration != 1) {
				endPoints.removeAll(endPoints);
				endPoints.addAll(nextEndPoints);
			}
			nextEndPoints.removeAll(nextEndPoints);
			Iterator<int[]> iter = endPoints.iterator();
			
			while (iter.hasNext()) {
				int[] endPoint = (int[]) iter.next();
				creationIndexX = endPoint[0];
				creationIndexY = endPoint[1];
				creationIndexZ = endPoint[2];
				int corridorLength = MIN_CORRIDOR_LENGTH + rnd.nextInt(MAX_CORRIDOR_LENGTH - MIN_CORRIDOR_LENGTH);
				Collection<int[]> addedSegments = new ArrayList<int[]>();
				int initCreationIndexX = creationIndexX;
				int initCreationIndexY = creationIndexY;
				int initCreationIndexZ = creationIndexZ;

				int MAX_ATTEMPTS = 10;
				int attempt = 1;
				for (int corridorSegmentCount = 0; corridorSegmentCount <= corridorLength; corridorSegmentCount++) {
					MapFragment fragNX = mapArray[creationIndexX - 1][creationIndexY][creationIndexZ];
					MapFragment fragX = mapArray[creationIndexX + 1][creationIndexY][creationIndexZ];
					MapFragment fragNY = mapArray[creationIndexX][creationIndexY - 1][creationIndexZ];
					MapFragment fragY = mapArray[creationIndexX][creationIndexY + 1][creationIndexZ];
					CandidateFinder finder;
					int[] currentCoordinate = { creationIndexX, creationIndexY, creationIndexZ };
					if (!fragNX.equals(Map.MapFragment.EMPTY) && !fragNX.equals(Map.MapFragment.DEAD_END_NX)) {
						finder = new CandidateFinderByPreviousNX(mapArray, currentCoordinate);
					} else if (!fragX.equals(Map.MapFragment.EMPTY) && !fragX.equals(Map.MapFragment.DEAD_END_X)) {
						finder = new CandidateFinderByPreviousX(mapArray, currentCoordinate);
					} else if (!fragY.equals(Map.MapFragment.EMPTY) && !fragX.equals(Map.MapFragment.DEAD_END_Y)) {
						finder = new CandidateFinderByPreviousY(mapArray, currentCoordinate);
					} else if (!fragNY.equals(Map.MapFragment.EMPTY) && !fragX.equals(Map.MapFragment.DEAD_END_NY)) {
						finder = new CandidateFinderByPreviousNY(mapArray, currentCoordinate);
					} else {
						throw new Exception("endPoint has no set adjacent field");
					}
					ArrayList<Map.MapFragment> possibilities = finder.findCandidates(corridorLength, corridorSegmentCount);
					if (possibilities == null) {
						// TODO hierhin aus finder verlagern
					} else {
						if(possibilities.contains(MapFragment.CORRIDOR_X)) {
							possibilities.add(MapFragment.CORRIDOR_X);
							possibilities.add(MapFragment.CORRIDOR_X);
						} else if(possibilities.contains(MapFragment.CORRIDOR_Y)) {
							possibilities.add(MapFragment.CORRIDOR_Y);
							possibilities.add(MapFragment.CORRIDOR_Y);
						}
					}
					int[] previousCoordinates = finder.getPreviousCoordinates();
					int countPossibilities = possibilities.size();
					if (countPossibilities == 0) {
						attempt++;
						System.out.println("Attempt: " + attempt);
						if (MAX_ATTEMPTS == attempt) {
							MapTestRenderer renderer = new MapTestRenderer();
							renderer.render(mapArray, Map.MAP_SIZE_X, Map.MAP_SIZE_Y, Map.MAP_SIZE_Z);
							throw new Exception("unpossible to build map");
						}
						creationIndexX = initCreationIndexX;
						creationIndexY = initCreationIndexY;
						creationIndexZ = initCreationIndexZ;

						Iterator addedIter = addedSegments.iterator();
						while (addedIter.hasNext()) {
							int[] addedSegment = (int[]) addedIter.next();
							setMapField(addedSegment[0], addedSegment[1], addedSegment[2], MapFragment.EMPTY);
						}
						addedSegments.removeAll(addedSegments);
						corridorSegmentCount = 0;

					} else {
						Collection<int[]> newEndPoints = updateMapAndIndex(possibilities, previousCoordinates, map);
						String possibilitiesString = possibilities == null ? "Null" : possibilities.toString();
						String newEndPointsString = newEndPoints == null ? "Null" : newEndPoints.toString();
						System.out.println("posibilities: " + possibilitiesString + ", newEndPoints: " + newEndPointsString);
						if (newEndPoints != null) {
							nextEndPoints.addAll(newEndPoints); // TODO wird
																// nicht
																// gesetzt?
						}
						addedSegments.add(new int[] { creationIndexX, creationIndexY, creationIndexZ });
					}
					System.out.println("Fragment Count:" + corridorSegmentCount + " of " + corridorLength + ", Iteration: " + iteration + "/" + ITERATIONS
							+ ", class: " + finder.getClass().getName());
				}
				boolean lastIterationSuccesful = true;
				if ((iteration == ITERATIONS) && (lastIterationSuccesful)) {
					wasMapCreationSuccessful = true;
				}
			}
			System.out.println("Iteration completed: " + iteration);
		}
		return map;
	}

	private void setMapField(int x, int y, int z, MapFragment fieldType) {
		System.out.println("added to x=" + x + ",y=" + y + ", z:" + z + ", type:" + fieldType);
		mapArray[x][y][z] = fieldType;
	}

	private Collection<int[]> determineEndPoints(MapFragment fragmentToAdd, int[] previousCoordinates, int[] currentCoordinate) throws Exception {
		Collection<int[]> endPoints = new ArrayList<int[]>();
		if (MapFragment.T_CROSSING_NX.equals(fragmentToAdd)) {
			if (previousCoordinates[0] < currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			} else if (previousCoordinates[1] < currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
			} else if (previousCoordinates[1] > currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			}
		} else if (MapFragment.T_CROSSING_X.equals(fragmentToAdd)) {
			if (previousCoordinates[0] > currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			} else if (previousCoordinates[1] < currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
			} else if (previousCoordinates[1] > currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			}
		} else if (MapFragment.T_CROSSING_Y.equals(fragmentToAdd)) {
			if (previousCoordinates[1] > currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
			} else if (previousCoordinates[0] < currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
			} else if (previousCoordinates[0] > currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
			}
		} else if (MapFragment.T_CROSSING_NY.equals(fragmentToAdd)) {
			if (previousCoordinates[1] < currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
			} else if (previousCoordinates[0] < currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			} else if (previousCoordinates[0] > currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			}
		} else if (MapFragment.CROSSING.equals(fragmentToAdd)) {
			if (previousCoordinates[1] < currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
			} else if (previousCoordinates[1] > currentCoordinate[1]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			} else if (previousCoordinates[0] < currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0] + 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			} else if (previousCoordinates[0] > currentCoordinate[0]) {
				endPoints.add(new int[] { currentCoordinate[0] - 1, currentCoordinate[1], currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] + 1, currentCoordinate[2] });
				endPoints.add(new int[] { currentCoordinate[0], currentCoordinate[1] - 1, currentCoordinate[2] });
			}
		} else {
			throw new Exception("Bei der Bestimmung der Endpunkte ist ein Fehler aufgetreten. Das hinzugefügte Fragment war: " + fragmentToAdd);
		}
		return endPoints;
	}

	private Collection<int[]> updateMapAndIndex(ArrayList<Map.MapFragment> possibilities, int[] previousCoordinates, Map map) throws Exception {
		if (possibilities.contains(MapFragment.CORRIDOR_X)) {
			possibilities.add(MapFragment.CORRIDOR_X);
		}
		if (possibilities.contains(MapFragment.CORRIDOR_Y)) {
			possibilities.add(MapFragment.CORRIDOR_Y);
		}
		int countPossibilities = possibilities.size();
		int usePossibilityIndex = rnd.nextInt(countPossibilities);
		MapFragment fragmentToAdd = possibilities.get(usePossibilityIndex);
		setMapField(creationIndexX, creationIndexY, creationIndexZ, fragmentToAdd);
		switch (fragmentToAdd) {
		case CORRIDOR_X:
			if (creationIndexX < previousCoordinates[0]) {
				creationIndexX--;
			} else if (creationIndexX > previousCoordinates[0]) {
				creationIndexX++;
			} else {
				throw new Exception(buildErrorStringForNoCrossing(MapFragment.CORRIDOR_X, previousCoordinates));
			}
			break;
		case CORRIDOR_Y:
			if (creationIndexY < previousCoordinates[1]) {
				creationIndexY--;
			} else if (creationIndexY > previousCoordinates[1]) {
				creationIndexY++;
			} else {
				throw new Exception(buildErrorStringForNoCrossing(MapFragment.CORRIDOR_X, previousCoordinates));
			}
			break;
		case CURVE_X_Y:
			if (creationIndexX < previousCoordinates[0]) {
				creationIndexY++;
			} else if (creationIndexY < previousCoordinates[1]) {
				creationIndexX++;
			} else {
				throw new Exception(buildErrorStringForNoCrossing(MapFragment.CURVE_NX_Y, previousCoordinates));
			}
			break;
		case CURVE_NX_Y:
			if (creationIndexX > previousCoordinates[0]) {
				creationIndexY++;
			} else if (creationIndexY < previousCoordinates[1]) {
				creationIndexX--;
			} else {
				throw new Exception(buildErrorStringForNoCrossing(MapFragment.CURVE_NX_Y, previousCoordinates));
			}
			break;
		case CURVE_X_NY:
			if (creationIndexX < previousCoordinates[0]) {
				creationIndexY--;
			} else if (creationIndexY > previousCoordinates[1]) {
				creationIndexX++;
			} else {
				throw new Exception(buildErrorStringForNoCrossing(MapFragment.CURVE_NX_Y, previousCoordinates));
			}
			break;
		case CURVE_NX_NY:
			if (creationIndexX > previousCoordinates[0]) {
				creationIndexY--;
			} else if (creationIndexY > previousCoordinates[1]) {
				creationIndexX--;
			} else {
				throw new Exception(buildErrorStringForNoCrossing(MapFragment.CURVE_NX_NY, previousCoordinates));
			}
			break;
		case T_CROSSING_NX:
			return determineEndPointsOnCrossing(previousCoordinates, fragmentToAdd);
		case T_CROSSING_X:
			return determineEndPointsOnCrossing(previousCoordinates, fragmentToAdd);
		case T_CROSSING_NY:
			return determineEndPointsOnCrossing(previousCoordinates, fragmentToAdd);
		case T_CROSSING_Y:
			return determineEndPointsOnCrossing(previousCoordinates, fragmentToAdd);
		default:
			break;
		}
		return null;
	}

	private String buildErrorStringForNoCrossing(MapFragment mapFragment, int[] previousCoordinates) {
		MapFragment previous = mapArray[previousCoordinates[0]][previousCoordinates[1]][previousCoordinates[2]];
		return mapFragment.toString() + " was added but previous Position does not fit. Previous: " + previous;
	}

	private Collection<int[]> determineEndPointsOnCrossing(int[] previousCoordinates, MapFragment fragmentToAdd) throws Exception {
		int[] currentCoordinate = { creationIndexX, creationIndexY, creationIndexZ };
		return determineEndPoints(fragmentToAdd, previousCoordinates, currentCoordinate);
	}

	private void initEmpty(MapFragment[][][] mapArray, Map map) {
		for (int z = 0; z < map.MAP_SIZE_Z; z++) {
			for (int y = 0; y < map.MAP_SIZE_Y; y++) {
				for (int x = 0; x < map.MAP_SIZE_X; x++) {
					mapArray[x][y][z] = Map.MapFragment.EMPTY;
				}
			}
		}
	}

	private int[] markStartFields(Map map) {
		int playerStartX = map.PLAYER_START_X;
		int playerStartY = map.PLAYER_START_Y;
		int playerStartZ = map.PLAYER_START_Z;
		setMapField(playerStartX, playerStartY, playerStartZ, Map.MapFragment.START);
		setMapField(playerStartX + 1, playerStartY, playerStartZ, Map.MapFragment.START);
		setMapField(playerStartX - 1, playerStartY, playerStartZ, Map.MapFragment.START);
		setMapField(playerStartX, playerStartY - 1, playerStartZ, Map.MapFragment.START);
		setMapField(playerStartX + 1, playerStartY - 1, playerStartZ, Map.MapFragment.START);
		setMapField(playerStartX - 1, playerStartY - 1, playerStartZ, Map.MapFragment.START);
		setMapField(playerStartX, playerStartY + 1, playerStartZ, Map.MapFragment.CORRIDOR_Y);
		setMapField(playerStartX, playerStartY + 2, playerStartZ, Map.MapFragment.CORRIDOR_Y);
		setMapField(playerStartX, playerStartY + 3, playerStartZ, Map.MapFragment.CORRIDOR_Y);
		int[] startPoint = { playerStartX, playerStartY + 4, playerStartZ };
		return startPoint;
	}
}
